#import futures from python 3.* also available in 2.6.*
from __future__ import print_function
from __future__ import division

import pyLinesUtils as utils
import pyResMan as resman
import pyLinesAlgorithms as algo

import pygame as pg
import pygame.locals as pgl

import random
import sys
import os
import copy
import math

class GameState(object):
	"""Base class for game state object"""

	def __init__(self, gameInstance):
		self.gameInstance = gameInstance

	def update(self, delta):
		pass

	def check_input(self, delta):
		pass

	def is_end(self):
		return False

class AddBallsState(GameState):
	def __init__(self, gameInstance):
		GameState.__init__(gameInstance)

	def update(self, delta):
		pass


class BallConfig(object):
	"""Ball configuration."""

	def __init__(self):
		self.radius = 20
		self.dim = (60, 60)
		self.active_dim = (70, 70)
		self.active_radius = 24
		self.scale_time = 1.0
		self.colors = { 'RED': (255, 0, 0), 'GREEN': (0, 255, 0), 'BLUE': (0, 0, 255), 'YELLOW': (255, 200, 0) }
		self.textures = { 'RED': 'red_ball', 'GREEN': 'green_ball', 'BLUE': 'blue_ball', 'YELLOW': 'yellow_ball' }
		self.res_man = None
		self.texture = None

class Ball(object):
	"""Ball :)"""

	def __init__(self, color, config, pos=None):
		self.color = color
		self.config = config
		self.pos = pos
		self.active = False
		self.scaleTimer = 0.0

		if self.config.res_man is not None:
			self.texture = self.config.res_man.get_texture(self.config.textures[self.color])
		else:
			self.texture = None

	def move_to(self, pos):
		self.pos = pos

	def render(self, surface):
		if self.pos is not None:
			if self.texture is not None:
				scaleProgress = math.sin(self.scaleTimer / self.config.scale_time * math.pi)
				dim = (int(utils.lerp(self.config.dim[0], self.config.active_dim[0], scaleProgress)),
				 int(utils.lerp(self.config.dim[1], self.config.active_dim[1], scaleProgress)))

				tex_surface = pg.Surface(dim, 0, self.texture)
				tex_surface = pg.transform.smoothscale(self.texture, dim)

				pos = (self.pos[0] - dim[0] * 0.5,  self.pos[1] - dim[1] * 0.5)

				surface.blit(tex_surface, pos)
			else:
				radius = int(utils.lerp(self.config.radius, self.config.active_radius,
			 self.scaleTimer / self.config.scale_time))
				pg.draw.circle(surface, self.config.colors[self.color], self.pos, radius)

	def set_active(self, active):
		self.active = active

	def update(self, deltaTime):
		#print(deltaTime)
		if self.active is True:
			self.scaleTimer += deltaTime
			if self.scaleTimer >= self.config.scale_time:
				self.scaleTimer -= self.config.scale_time
		elif self.active is False and self.scaleTimer > 0.0:
			self.scaleTimer -= deltaTime
			if self.scaleTimer <= 0.0:
				self.scaleTimer = 0.0

class Cell(object):
	"""Game field cell"""

	def is_free(self):
		return self.ball == None

	def __init__(self, gridCoord, screenCoord):
		self.ball = None
		self.gridCoord = gridCoord
		self.screenCoord = screenCoord

	def set_ball(self, ball):
		self.ball = ball
		if self.ball is not None:
			self.ball.pos = self.screenCoord

class LinesConfig(object):
	"""Lines game gonfiguration """

	"""Video, gameplay settings etc. """

	def __init__(self):
		self.resolution = (1024, 768)
		self.video_mode_flags = pg.DOUBLEBUF | pg.HWSURFACE | pg.RESIZABLE
		self.fullscreen_flags = pg.FULLSCREEN | pg.RESIZABLE

		"""Place new balls just after player removed balls."""
		self.new_balls_after_disappearing = False
		self.num_balls_per_turn = 3
		self.points_per_ball = 5
		self.min_balls_to_remove = 5

		self.line_color = (255,255,255)
		self.line_width = 3
		self.grid_bg_color = (72,72,64)
		self.bg_color = (192, 199, 199)

	def toggle_fullscreen_flags(self):
		self.video_mode_flags ^= self.fullscreen_flags

class Lines(object):
	"""Class representing lines game."""
	
	GRID_SIZE_H = 0.8
	GRID_SIZE_V = 0.5

	grid_wd = 6
	grid_ht = 10

	CELLS_COUNT = (9, 9)#horizontaly, verticaly

	BALLS_CONFIG = BallConfig()

	def __init__(self):
		self.display = None
		self.run = False
		self.game_clock = None
		system_font = None

		self.background = None
		self.grid = None
		self.CELL_SIZE = None

		self.states = []

		"""Cells are holded in two dimensional list [column][row]"""
		self.cells = []
		self.free_cells = []
		self.config = LinesConfig()
		self.selected_cell = None
		self.selected_cell_coords = None
		resman_settings = resman.ResManSettings('resources')
		resman_settings.tex_dir = 'textures'
		self.res_man = resman.ResMan(resman_settings)
		Lines.BALLS_CONFIG.res_man = self.res_man
		self.score = 0

	def loop(self):
		self.run = True
		while self.run == True:
			#print(self.run)
			self.game_clock.tick()
			self.check_input(pg.event.get())

			self.update()

			self.clear_screen()

			self.render()

			pg.display.flip()
			

	def check_input(self, events):
		#print("check input")
		for evt in events:
			if evt.type == pgl.QUIT or (evt.type == pgl.KEYDOWN and evt.key == pgl.K_q): 
				self.run = False
				print("QUIT..................")
			elif evt.type == pgl.KEYDOWN:
				if evt.key == pgl.K_f:
					self.config.toggle_fullscreen_flags()
					self.set_up_display()
					self.adjust_render_to_display(True)
				if evt.key == pgl.K_r:
					self.reset()
			elif evt.type == pgl.MOUSEBUTTONDOWN and evt.button == 1:
				self.process_mb_l_press(evt.pos)
			elif evt.type == pgl.VIDEORESIZE:
				self.config.resolution = (evt.w, evt.h)
				self.set_up_display()
				self.adjust_render_to_display(True)
			else:
				pass
				#print(evt)

	def process_mb_l_press(self, mouse_pos):
		"""Process Left Mouse button press."""
		cell_coords = self.coord_to_cell_coord(mouse_pos)
		if cell_coords is None:
			print('mbl coord none')
			return
		cell = self.cells[cell_coords[0]][cell_coords[1]]

		if cell_coords == self.selected_cell_coords:
			print('cell == self.selected_cell')
			return
		assert(cell is not None)

		if cell.is_free():
			print('cell is free')
			if self.selected_cell is not None:
				result = algo.find_path_BFS(self.selected_cell_coords, cell_coords,
				 self.cells, self.CELLS_COUNT)
				if result is not None:
					cell.set_ball(self.selected_cell.ball)
					self.selected_cell.set_ball(None)
					self.free_cells.append(self.selected_cell)
					self.free_cells.remove(cell)
					self.selected_cell = cell
					self.selected_cell_coords = cell_coords

					num_balls_removed = self.find_adjustent_balls()
					if num_balls_removed <= 0 or self.config.new_balls_after_disappearing:
						self.place_random_balls(self.config.num_balls_per_turn)
						num_balls_removed += self.find_adjustent_balls()

					self.score += num_balls_removed * self.config.points_per_ball
		else:
			print('cell is not free')
			if self.selected_cell is not None:
				self.selected_cell.ball.set_active(False)

			self.selected_cell = cell
			self.selected_cell_coords = cell_coords
			self.selected_cell.ball.set_active(True)

	def update(self):
		self.update_balls()

	def render(self):
		self.render_grid()
		fontText = self.system_font.render("FPS: {0:.3f}".format(self.game_clock.get_fps()), True, (0,0,64))

		textPos = fontText.get_rect()
		textPos.right = self.background.get_rect().right
		textPos.bottom = self.background.get_rect().bottom	

		self.background.blit(fontText, textPos)

		self.render_score()
		self.render_balls(self.background)
		self.display.blit(self.background, (0,0))

	def render_grid(self):
		gridRect = self.grid.get_rect()
		gridRect.center = self.background.get_rect().center

		y = 0
		x = 0
		
		while y <= self.CELLS_COUNT[1]:
			start = (0, self.CELL_SIZE[1] * y)
			end = (gridRect.right, self.CELL_SIZE[1] * y)
			
			pg.draw.line(self.grid, self.config.line_color, start, end, self.config.line_width)

			while x <= self.CELLS_COUNT[0]:
				start = (self.CELL_SIZE[0] * x, 0)
				end = (self.CELL_SIZE[0] * x, gridRect.bottom)
			
				pg.draw.line(self.grid, self.config.line_color, start, end, self.config.line_width)
				x = x + 1
			
			y = y + 1
		
		self.background.blit(self.grid, gridRect)

	def render_score(self):
		fontText = self.system_font.render("Score: {0}".format(self.score), True, (0,0,64))

		textPos = fontText.get_rect()
		bkg_rect = self.background.get_rect()
		textPos.right = bkg_rect.right - bkg_rect.width * 0.05
		textPos.top = bkg_rect.top + bkg_rect.height * 0.2

		self.background.blit(fontText, textPos)

	def clear_screen(self):
		self.background.fill(self.config.bg_color)
		self.grid.fill(self.config.grid_bg_color)

	def init(self):
		print("Lines initialization.")
		random.seed()
		pg.init()

		self.set_up_display()
		
		self.game_clock = pg.time.Clock()

		self.system_font = pg.font.Font(None, 36)
		
		print('Loading resources')
		self.res_man.load_textures(['blue_ball', 'green_ball', 'red_ball', 'violet_ball', 'yellow_ball'])

		self.adjust_render_to_display(False)

		self.init_cells()

		self.adjust_render_to_display(True)

	def set_up_display(self):
		#self.display = pg.display.set_mode((1024, 768), pg.FULLSCREEN | pg.DOUBLEBUF | pg.HWSURFACE)
		self.display = pg.display.set_mode(self.config.resolution, self.config.video_mode_flags)
		pg.display.set_caption("pyLines.")

	def adjust_render_to_display(self, adjust_balls_and_cells):
		wd = int(self.display.get_width() * self.GRID_SIZE_V)
		ht = int(self.display.get_height() * self.GRID_SIZE_H)
		self.grid = pg.Surface((wd, ht)).convert()
		self.background = pg.Surface(self.display.get_size()).convert()
		self.CELL_SIZE = (self.grid.get_rect().width / float(self.CELLS_COUNT[0]), self.grid.get_rect().height / float(self.CELLS_COUNT[1]))

		if adjust_balls_and_cells:
			self.adjust_balls_and_cells_to_display()


	def adjust_balls_and_cells_to_display(self):

		Lines.BALLS_CONFIG.radius = self.CELL_SIZE[0]
		Lines.BALLS_CONFIG.active_radius = int(float(Lines.BALLS_CONFIG.radius) * 10.3)

		dim = self.CELL_SIZE[0]
		Lines.BALLS_CONFIG.dim = (dim, dim)
		Lines.BALLS_CONFIG.active_dim = (int(float(dim) * 1.2), int(float(dim) * 1.2))

		for h in range(self.CELLS_COUNT[0]):
			for v in range(self.CELLS_COUNT[1]):
				cell = self.cells[h][v]
				cell.screenCoord = self.cell_to_coord((h, v))
				if not cell.is_free():
					cell.ball.pos = self.cell_to_coord((h, v))

	def init_cells(self):

		self.free_cells = []
		self.cells = []
		
		for h in range(self.CELLS_COUNT[0]):
			self.cells.append([])
			
			for v in range(self.CELLS_COUNT[1]):
				cell = Cell((h, v), self.cell_to_coord((h, v)))
				self.cells[h].append(cell)
				self.free_cells.append(cell)
				
				self.cells[h][v].set_ball(None)

	def cell_to_coord(self, cellCoord):
		gridRect = self.grid.get_rect()
		gridRect.center = self.background.get_rect().center

		x = gridRect.left + (cellCoord[0] * self.CELL_SIZE[0]) + self.CELL_SIZE[0] / 2
		y = gridRect.top + (cellCoord[1] * self.CELL_SIZE[1]) + self.CELL_SIZE[1] / 2

		return (x, y)

	def coord_to_cell(self, coord):
		cell_coords = coord_to_cell_coord(coord)
		if cell_coords is None:
			return None

		return self.cells[cell_coords[0]][cell_coords[1]]
		gridRect = self.grid.get_rect()
		gridRect.center = self.background.get_rect().center

	def coord_to_cell_coord(self, coord):
		gridRect = self.grid.get_rect()
		gridRect.center = self.background.get_rect().center

		if gridRect.collidepoint(coord) == False:
			print('doesn\'t contain')
			return None

		x = int(((coord[0] - gridRect.left) / self.CELL_SIZE[0]))
		y = int(((coord[1] - gridRect.top) / self.CELL_SIZE[1]))

		return (x, y)

	def render_balls(self, surface):
		for h in range(self.CELLS_COUNT[0]):
			for v in range(self.CELLS_COUNT[1]):
				if (self.cells[h][v].is_free() == False):
					self.cells[h][v].ball.render(surface)

	def update_balls(self):
		for h in range(self.CELLS_COUNT[0]):
			for v in range(self.CELLS_COUNT[1]):
				if (self.cells[h][v].is_free() == False):
					self.cells[h][v].ball.update(self.game_clock.get_time() / 1000.0)

	def place_random_balls(self, num):
		for i in range(min(num, len(self.free_cells))):
			randomCell = random.randint(0, len(self.free_cells) - 1)
			print(randomCell)
			cell = self.free_cells[randomCell]
			self.free_cells.remove(cell)
			print(len(Lines.BALLS_CONFIG.colors))
			colorKey = random.choice(Lines.BALLS_CONFIG.colors.keys())
			print(colorKey) 
			ball = Ball(colorKey, Lines.BALLS_CONFIG, self.cell_to_coord(cell.gridCoord))
			cell.set_ball(ball)
			self.cells[cell.gridCoord[0]][cell.gridCoord[1]] = cell

	def reset(self):
		self.init_cells()
		self.place_random_balls(5)
		self.selected_cell = None
		self.selected_cell_coords = None
		self.score = 0

	def find_adjustent_balls(self):
		num_balls_removed = 0
		for h in range(self.CELLS_COUNT[0]):
			for v in range(self.CELLS_COUNT[1]):
				start_cell = self.cells[h][v]
				if (start_cell.is_free() == False):
					adjustents = [[start_cell], [start_cell], [start_cell], [start_cell]]

					def h_stepper(horizontal, vertical):
						return horizontal + 1, vertical

					self.find_adjustent_balls_in_dir(start_cell, (h + 1, v), adjustents[0], h_stepper)

					def v_stepper(hh, vv):
						return hh, vv + 1

					self.find_adjustent_balls_in_dir(start_cell, (h, v + 1), adjustents[1], v_stepper)

					def d1_stepper(hh, vv):
						return hh + 1, vv + 1

					self.find_adjustent_balls_in_dir(start_cell, (h + 1, v + 1), adjustents[2], d1_stepper)

					def d2_stepper(hh, vv):
						return hh - 1, vv + 1

					self.find_adjustent_balls_in_dir(start_cell, (h - 1, v + 1), adjustents[3], d2_stepper)

					for a in adjustents:
						if len(a) >= 5:
							num_balls_removed += len(a)
							self.remove_adjustent_balls(a)

		return num_balls_removed

	def find_adjustent_balls_in_dir(self, cell, start_coords, adjustents, step):
		h = start_coords[0]
		v = start_coords[1]
		
		while(h >= 0 and h < self.CELLS_COUNT[0] and v >= 0 and v < self.CELLS_COUNT[1] and
		self.cells[h][v].is_free() == False and cell.ball.color == self.cells[h][v].ball.color):
			adjustents.append(self.cells[h][v])
			h, v = step(h, v)

	def remove_adjustent_balls(self, cells):
		for cell in cells:
			cell.set_ball(None)
			self.free_cells.append(cell)
			if cell == self.selected_cell:
				self.selected_cell = None
				self.select_cell_coords = None
				
		
						

def main():
	lines = Lines()
	lines.init()
	lines.loop()
	sys.exit(0)
	

if __name__ == "__main__":
	main()
